﻿using System.Collections.Generic;
using System.Linq;
using System;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Security.Permissions;
using System.Threading;

using MyUtils.PInvoke.Consts;
using MyUtils.PInvoke.Structs;

namespace MyUtils.PInvoke
{
	public sealed class MyFolderBrowserDialog : CommonDialog
	{
		// Fields
		private BrowseFolderCallbackProc _callback;
		private string _descriptionText;
		private Environment.SpecialFolder _rootFolder;
		private string _selectedPath;
		private bool _selectedPathNeedsCheck;
		private bool _showNewFolderButton;
		private bool _showEditBox;
		private bool _showBothFilesAndFolders;
		private bool _newStyle = true;
		private bool _showFullPathInEditBox = true;
		private bool _dontIncludeNetworkFoldersBelowDomainLevel;
		private uint _uiFlags;
		private IntPtr _hwndEdit;
		private IntPtr _rootFolderLocation;

//		public new event EventHandler HelpRequest
//		{
//			add
//			{
//				base.HelpRequest += value;
//			}
//			remove
//			{
//				base.HelpRequest -= value;
//			}
//		}

		public MyFolderBrowserDialog()
		{
			Reset();
		}

		public static MyFolderBrowserDialog PrinterBrowser()
		{
			var x = new MyFolderBrowserDialog();
			// avoid MBRO comppiler warning when passing _rootFolderLocation as a ref:
			x.BecomePrinterBrowser();
			return x;
		}

		public static MyFolderBrowserDialog ComputerBrowser()
		{
			var x = new MyFolderBrowserDialog();
			// avoid MBRO comppiler warning when passing _rootFolderLocation as a ref:
			x.BecomeComputerBrowser();
			return x;
		}

		// Helpers
		private void BecomePrinterBrowser()
		{
			_uiFlags += PInvoke.BIF_BROWSEFORPRINTER;
			Description = "Select a printer:";
			PInvoke.SHGetSpecialFolderLocation(IntPtr.Zero, CSIDL.PRINTERS, ref _rootFolderLocation);
			ShowNewFolderButton = false;
			ShowEditBox = false;
		}

		private void BecomeComputerBrowser()
		{
			_uiFlags += PInvoke.BIF_BROWSEFORCOMPUTER;
			Description = "Select a computer:";
			PInvoke.SHGetSpecialFolderLocation(IntPtr.Zero, CSIDL.NETWORK, ref _rootFolderLocation);
			ShowNewFolderButton = false;
			ShowEditBox = false;
		}

		private int FolderBrowserCallback(IntPtr hwnd, uint msg, IntPtr lParam, IntPtr lpData)
		{
			switch (msg)
			{
				case PInvoke.BFFM_INITIALIZED:
					// hwnd.MakeGlassWindow();

//					if (PInvoke.GlassEnabled)
//					{
//						SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) ^ WS_EX_LAYERED);
//						SetLayeredWindowAttributes(hwnd, 0, 210, 2);
//					}

					if (_selectedPath.Length != 0)
					{
						PInvoke.SendMessage(new HandleRef(null, hwnd), PInvoke.BFFM_SETSELECTIONW, 1, this._selectedPath);
						if (this._showEditBox && this._showFullPathInEditBox)
						{
							// get handle to the Edit box inside the Folder Browser Dialog
							_hwndEdit = PInvoke.FindWindowEx(new HandleRef(null, hwnd), IntPtr.Zero, "Edit", null);
							PInvoke.SetWindowText(_hwndEdit, this._selectedPath);
						}
					}
					break;

				case PInvoke.BFFM_SELCHANGED:
					IntPtr pidl = lParam;
					if (pidl != IntPtr.Zero)
					{
						if (((_uiFlags & PInvoke.BIF_BROWSEFORPRINTER) == PInvoke.BIF_BROWSEFORPRINTER) ||
							((_uiFlags & PInvoke.BIF_BROWSEFORCOMPUTER) == PInvoke.BIF_BROWSEFORCOMPUTER))
						{
							// we're browsing for a printer or computer, enable the OK button unconditionally.
							PInvoke.SendMessage(new HandleRef(null, hwnd), PInvoke.BFFM_ENABLEOK, 0, 1);
						}
						else
						{
							//IntPtr pszPath = Marshal.AllocHGlobal(MAX_PATH * Marshal.SystemDefaultCharSize);
							var displayedPathB = new StringBuilder(C.MAX_PATH);
							bool haveValidPath = PInvoke.SHGetPathFromIDList(pidl, displayedPathB);
							var displayedPath = displayedPathB.ToString();
							//String displayedPath = Marshal.PtrToStringAuto(pszPath);
							//Marshal.FreeHGlobal(pszPath);

							// whether to enable the OK button or not. (if file is valid)
							PInvoke.SendMessage(new HandleRef(null, hwnd), PInvoke.BFFM_ENABLEOK, 0, haveValidPath ? 1 : 0);

							// Maybe set the Edit Box text to the Full Folder path
							if (haveValidPath && !String.IsNullOrEmpty(displayedPath))
							{
								if (_showEditBox && _showFullPathInEditBox)
								{
									if (_hwndEdit != IntPtr.Zero)
										PInvoke.SetWindowText(_hwndEdit, displayedPath);
								}

								if ((_uiFlags & PInvoke.BIF_STATUSTEXT) == PInvoke.BIF_STATUSTEXT)
									PInvoke.SendMessage(new HandleRef(null, hwnd), PInvoke.BFFM_SETSTATUSTEXT, 0, displayedPath);
							}
						}
					}
					break;
			}
			return 0;
		}

		public override void Reset()
		{
			_rootFolder = 0;
			_descriptionText = string.Empty;
			_selectedPath = string.Empty;
			_selectedPathNeedsCheck = false;
			_showNewFolderButton = true;
			_showEditBox = true;
			_newStyle = true;
			_dontIncludeNetworkFoldersBelowDomainLevel = false;
			_hwndEdit = IntPtr.Zero;
			_rootFolderLocation = IntPtr.Zero;
		}

		protected override bool RunDialog(IntPtr hWndOwner)
		{
			bool result = false;
			if (_rootFolderLocation == IntPtr.Zero)
			{
				PInvoke.SHGetSpecialFolderLocation(hWndOwner, (int)_rootFolder, ref _rootFolderLocation);
				if (_rootFolderLocation == IntPtr.Zero)
				{
					PInvoke.SHGetSpecialFolderLocation(hWndOwner, 0, ref _rootFolderLocation);
					if (_rootFolderLocation == IntPtr.Zero)
					{
						throw new InvalidOperationException("FolderBrowserDialogNoRootFolder");
					}
				}
			}
			_hwndEdit = IntPtr.Zero;
			//_uiFlags = 0;
			if (_dontIncludeNetworkFoldersBelowDomainLevel)
				_uiFlags += PInvoke.BIF_DONTGOBELOWDOMAIN;
			if (_newStyle)
				_uiFlags += PInvoke.BIF_NEWDIALOGSTYLE;
			if (!_showNewFolderButton)
				_uiFlags += PInvoke.BIF_NONEWFOLDERBUTTON;
			if (_showEditBox)
				_uiFlags += PInvoke.BIF_EDITBOX;
			if (_showBothFilesAndFolders)
				_uiFlags += PInvoke.BIF_BROWSEINCLUDEFILES;


			if (Control.CheckForIllegalCrossThreadCalls && (Application.OleRequired() != ApartmentState.STA))
			{
				throw new ThreadStateException("ThreadMustBeSTA");
			}
			try
			{
				var browseInfo = BrowseInfo.New();
				_callback = FolderBrowserCallback;
				browseInfo.pidlRoot = _rootFolderLocation;
				browseInfo.Owner = hWndOwner;
				browseInfo.Title = _descriptionText;
				browseInfo.Flags = _uiFlags;
				browseInfo.callback = _callback;
				var pidl = PInvoke.SHBrowseForFolder(ref browseInfo);

				if (((_uiFlags & PInvoke.BIF_BROWSEFORPRINTER) == PInvoke.BIF_BROWSEFORPRINTER) ||
				((_uiFlags & PInvoke.BIF_BROWSEFORCOMPUTER) == PInvoke.BIF_BROWSEFORCOMPUTER))
				{
					_selectedPath = browseInfo.pszDisplayName;
					result = true;
				}
				else
				{
					if (pidl != IntPtr.Zero)
					{
						//throw new NotImplementedException();
						var sb = new StringBuilder(C.MAX_PATH);
						PInvoke.SHGetPathFromIDList(pidl, sb);
						_selectedPathNeedsCheck = true;
						_selectedPath = sb.ToString();
						result = true;
						var sHMalloc = PInvoke.GetSHMalloc();
						sHMalloc.Free(pidl);
					}
				}
			}
			finally
			{
				var sHMalloc = PInvoke.GetSHMalloc();
				sHMalloc.Free(_rootFolderLocation);
				_rootFolderLocation = IntPtr.Zero;
				_callback = null;
			}
			return result;
		}

		// Properties
		//[SRDescription("FolderBrowserDialogDescription"), SRCategory("CatFolderBrowsing"), Browsable(true), DefaultValue(""), Localizable(true)]

		/// <summary>
		/// This description appears near the top of the dialog box, providing direction to the user.
		/// </summary>
		public string Description
		{
			get
			{
				return this._descriptionText;
			}
			set
			{
				this._descriptionText = value ?? string.Empty;
			}
		}

		//[Localizable(false), SRCategory("CatFolderBrowsing"), SRDescription("FolderBrowserDialogRootFolder"), TypeConverter(typeof(SpecialFolderEnumConverter)), Browsable(true), DefaultValue(0)]
		public Environment.SpecialFolder RootFolder
		{
			get
			{
				return this._rootFolder;
			}
			set
			{
				if (!Enum.IsDefined(typeof(Environment.SpecialFolder), value))
				{
					throw new InvalidEnumArgumentException("value", (int)value, typeof(Environment.SpecialFolder));
				}
				this._rootFolder = value;
			}
		}

		//[Browsable(true), SRDescription("FolderBrowserDialogSelectedPath"), SRCategory("CatFolderBrowsing"), DefaultValue(""), Editor("System.Windows.Forms.Design.SelectedPathEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor)), Localizable(true)]

		/// <summary>
		/// Set or get the selected path.  
		/// </summary>
		public string SelectedPath
		{
			get
			{
				if (((!string.IsNullOrEmpty(_selectedPath))) && _selectedPathNeedsCheck)
				{
					new FileIOPermission(FileIOPermissionAccess.PathDiscovery, _selectedPath).Demand();
					_selectedPathNeedsCheck = false;
				}
				return _selectedPath;
			}
			set
			{
				_selectedPath = value ?? string.Empty;
				_selectedPathNeedsCheck = true;
			}
		}

		//[SRDescription("FolderBrowserDialogShowNewFolderButton"), Localizable(false), Browsable(true), DefaultValue(true), SRCategory("CatFolderBrowsing")]

		/// <summary>
		/// Enable or disable the "New Folder" button in the browser dialog.
		/// </summary>
		public bool ShowNewFolderButton
		{
			get
			{
				return this._showNewFolderButton;
			}
			set
			{
				this._showNewFolderButton = value;
			}
		}

		/// <summary>
		/// Show an "edit box" in the folder browser.
		/// </summary>
		/// <remarks>
		/// The "edit box" normally shows the name of the selected folder.  
		/// The user may also type a pathname directly into the edit box.  
		/// </remarks>
		/// <seealso cref="ShowFullPathInEditBox"/>
		public bool ShowEditBox
		{
			get
			{
				return this._showEditBox;
			}
			set
			{
				this._showEditBox = value;
			}
		}

		/// <summary>
		/// Set whether to use the New Folder Browser dialog style.
		/// </summary>
		/// <remarks>
		/// The new style is resizable and includes a "New Folder" button.
		/// </remarks>
		public bool NewStyle
		{
			get
			{
				return this._newStyle;
			}
			set
			{
				this._newStyle = value;
			}
		}


		public bool DontIncludeNetworkFoldersBelowDomainLevel
		{
			get { return _dontIncludeNetworkFoldersBelowDomainLevel; }
			set { _dontIncludeNetworkFoldersBelowDomainLevel = value; }
		}

		/// <summary>
		/// Show the full path in the edit box as the user selects it. 
		/// </summary>
		/// <remarks>
		/// This works only if ShowEditBox is also set to true. 
		/// </remarks>
		public bool ShowFullPathInEditBox
		{
			get { return _showFullPathInEditBox; }
			set { _showFullPathInEditBox = value; }
		}

		public bool ShowBothFilesAndFolders
		{
			get { return _showBothFilesAndFolders; }
			set { _showBothFilesAndFolders = value; }
		}
	}
}

